-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.
local context = require "mc.context"
local log = require "mc.logging"
local custom_msg = require "messages.custom"
local base_msg = require "messages.base"
local mdb_access = require "route_mapper.mdb_access"
local m = {}

function m.get_fqdn(host_name, domain_name)
    if host_name == nil or domain_name == nil then
        return nil
    end

    if host_name == "" then
        return domain_name
    elseif domain_name == "" then
        return host_name
    end

    return host_name .. "." .. domain_name
end

-- LDAP资源树
local LDAP_MDB_INTF = "bmc.kepler.AccountService.LDAP.LDAPController"

function m.get_ldap_domain(ldap_state, ldap_ctrl_paths)
    if ldap_state == false or ldap_ctrl_paths == nil then
        return nil
    end

    local ldap_domain = {}
    for _, path in ipairs(ldap_ctrl_paths) do
        local ldap_ctrl_obj = mdb.get_object(bus, path, LDAP_MDB_INTF)
        if ldap_ctrl_obj.UserDomain ~= "" then
            ldap_domain[#ldap_domain + 1] = ldap_ctrl_obj.UserDomain
        end
    end

    return ldap_domain
end


local SESSIONS_PATH<const> = "/bmc/kepler/SessionService/Sessions"
local SESSIONS_INTF<const> = "bmc.kepler.SessionService.Sessions"
local SESSION_INTF<const> = "bmc.kepler.SessionService.Session"
local IAMENUM_SESSIONTYPE_GUI<const> = 0
local IAMENUM_SESSIONLOGOUTTYPE_SESSIONRELOGIN<const> = 3
-- 双因素登录响应码
local RSP_LOGIN_BY_CERT = 0x1
local RSP_LOGIN_FAILED = 0x2
local function del_old_session(ip, bus, old_cookie, user_name)
    if old_cookie then
        local token = old_cookie:match("^SessionId=(.*)")
        if not token then
            return
        end

        local session_service_obj = mdb_access.get_object(bus, SESSIONS_PATH, SESSIONS_INTF)
        local is_validate_ok, validate_rsp = pcall(function()
            return session_service_obj:ValidateSession(context.new("WEB", "NA", ip),
            IAMENUM_SESSIONTYPE_GUI, token)
        end)
        if not is_validate_ok then
            return
        end

        local is_ok, session_obj = pcall(mdb_access.get_object, bus, SESSIONS_PATH .. "/" .. validate_rsp.SessionId,
            SESSION_INTF)
        is_ok = is_ok and session_obj.pcall:SessionDelete(
            context.new("WEB", user_name, ip or "127.0.0.1"), IAMENUM_SESSIONLOGOUTTYPE_SESSIONRELOGIN)
        if not is_ok then
            log:error("Delete old session failed")
        end
    end
end

local function parse_login_error(error_info)
    local err_map = {
        [custom_msg.AuthorizationFailedMessage.Name] = 2,
        [custom_msg.NoAccessMessage.Name] = 5,
        [base_msg.SessionLimitExceededMessage.Name] = 6,
        [custom_msg.UserLockedMessage.Name] = 7
    }
    local err_name = error_info.name
    return err_map[err_name]
end

local function get_username_by_session_id(bus, session_id)
    -- 获取session对象
    local ok, session_obj = pcall(mdb_access.get_object, bus, "/bmc/kepler/SessionService/Sessions/" .. session_id,
        SESSION_INTF)
    return ok and session_obj.UserName or ""
end

function m.get_new_session_by_cert(ReqHeader, mutual_state)
    local cookie = ReqHeader['cookie']
    local logoutbycert
    if cookie then
        logoutbycert = cookie:match('.*logoutbycert=(.*)')
    end
    if not mutual_state then
        return -- 未开启双因素认证登陆时不需要调用NewSessionByCert
    elseif mutual_state and logoutbycert ~= nil then
        return -- 此场景stateCode将返回3，不需要调用NewSessionByCert
    end
    local serial, issuer, subject, ip, cookie = ReqHeader["x-ssl_client_serial"] or "",
        ReqHeader["x-ssl-client-issuer"] or "", ReqHeader["x-ssl-client-subject"] or "",
        ReqHeader["x-real-ip"] or '127.0.0.1', ReqHeader["cookie"]
    local mutual_handler_obj = {Session = "", stateCode = 0, token = ""}
    local header = {}
    local ok, session_service_obj = pcall(mdb_access.get_object, bus, SESSIONS_PATH, SESSIONS_INTF)
    if ok then
        local ctx = context.new("WEB", "N/A", ip)
        local session_ok, session_result =
            session_service_obj.pcall:NewSessionByCert(ctx, serial, issuer, subject, ip, 0)
        if not session_ok then
            mutual_handler_obj.stateCode = parse_login_error(session_result) or RSP_LOGIN_FAILED
        else
            local user_name = get_username_by_session_id(bus, session_result.SessionId)
            if user_name ~= "" then
                del_old_session(ip, bus, cookie, user_name)
            end
            mutual_handler_obj.Session = session_result.SessionId or ""
            mutual_handler_obj.stateCode = RSP_LOGIN_BY_CERT
            mutual_handler_obj.token = session_result.CsrfToken
            header.set_cookie = string.format(
                    "SessionId=%s; Path=/; Secure; Httponly; SameSite=Strict",
                    session_result.Token or "")
        end
    else
        mutual_handler_obj.stateCode = 2
    end
    return {rsp = mutual_handler_obj, header = header}
end

return m
